﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using CommonExtensions.ObjectExtensions;
using CommonExtensions.ObjectExtensions.Reflection;
using Xunit;

namespace CommonExtensions.Tests
{
    public class ObjectExtensionsTests
    {
        [Fact]
        public void IsNullShouldReturnTrueIfObjectIsNull()
        {
            object obj = null;
            Assert.True(obj.IsNull());
        }

        [Fact]
        public void IsNullShouldReturnFalseIfObjectIsNotNull()
        {
            object obj = new TestObject(null);
            Assert.False(obj.IsNull());
        }

        [Fact]
        public void PutInListShouldThrowIfNull()
        {
            object obj = null;
            Assert.Throws<ArgumentNullException>(() => obj.PutInList());
        }

        [Fact]
        public void PutInListShouldPutObjectInAList()
        {
            object obj = new {};
            var list = obj.PutInList();

            Assert.Equal(1, list.Count);
            Assert.Equal(obj, list[0]);
        }

        [Fact]
        public void GetPropertyValueShouldThrowIfObjectIsNull()
        {
            object obj = null;
            Assert.Throws<ArgumentNullException>(() => obj.GetPropertyValue("PropertyName"));
        }

        [Fact]
        public void GetPropertyValueShouldThrowIfPropertyNameIsNull()
        {
            Assert.Throws<ArgumentNullException>(() => new TestObject(null).GetPropertyValue(null));
        }

        [Fact]
        public void GetPropertyValueShouldThrowIfPropertyNameIsEmpty()
        {
            Assert.Throws<ArgumentNullException>(() => new TestObject(null).GetPropertyValue(string.Empty));
        }

        [Fact]
        public void GetPropertyValueShouldThrowIfObjectDoesNotHaveGivenPropertyName()
        {
            Assert.Throws<InvalidOperationException>(() => new TestObject(null).GetPropertyValue(
                "ProbablyNotAPropertyWithThisName"));
        }

        [Fact]
        public void GetPropertyValueShouldReturnValueOfProperty()
        {
            object value = new {};
            Assert.Equal(value, new TestObject(value).GetPropertyValue("TestProperty"));
        }

        [Fact]
        public void GetPropertyAttributesShouldThrowIfObjectIsNull()
        {
            object obj = null;
            Assert.Throws<ArgumentNullException>(() => obj.GetPropertyAttributes<Attribute>("PropertyName"));
        }

        [Fact]
        public void GetPropertyAttributesShouldThrowIfPropertyNameIsNull()
        {
            Assert.Throws<ArgumentNullException>(() => new TestObject(null).GetPropertyAttributes<Attribute>(null));
        }

        [Fact]
        public void GetPropertyAttributesShouldThrowIfPropertyNameIsEmpty()
        {
            Assert.Throws<ArgumentNullException>(() => new TestObject(null).GetPropertyAttributes<Attribute>(
                string.Empty));
        }

        [Fact]
        public void GetPropertyAttributesShouldThrowIfObjectDoesNotHaveGivenPropertyName()
        {
            Assert.Throws<InvalidOperationException>(() => new TestObject(null).GetPropertyAttributes<Attribute>(
                "ProbablyNotAPropertyWithThisName"));
        }

        [Fact]
        public void GetPropertyAttributesShouldReturnEmptyCollectionIfPropertyHasNoAttributesOfGivenType()
        {
            var attributes = new TestObject(null).GetPropertyAttributes<Test3Attribute>("TestProperty");

            Assert.Equal(0, attributes.Count());
        }

        [Fact]
        public void GetPropertyAttributesShouldOnlyReturnAttributesOfGivenType()
        {
            var attributes = new TestObject(null).GetPropertyAttributes<TestAttribute>("TestProperty");

            Assert.Equal(1, attributes.Count());
        }

        [Fact]
        public void GetMethodsWithAttributeShouldThrowIfObjectIsNull()
        {
            object obj = null;
            Assert.Throws<ArgumentNullException>(() => obj.GetMethodsWithAttribute<Attribute>(a => true));
        }

        [Fact]
        public void GetMethodsWithAttributeShouldThrowIfMethodFilterProcIsNull()
        {
            Assert.Throws<ArgumentNullException>(() => new TestObject(null).GetMethodsWithAttribute<Attribute>(null));
        }

        [Fact]
        public void GetMethodsWithAttributeShouldReturnAllMethodsWithAttribute()
        {
            var methods = new TestObject(null).GetMethodsWithAttribute<TestAttribute>(a => a.TestProperty == "Test");

            Assert.Equal(2, methods.Count());
            Assert.Equal(1, methods.Where(m => m.Name == "Method1").Count());
            Assert.Equal(1, methods.Where(m => m.Name == "Method3").Count());
        }

        private class TestObject
        {
            [Test(null)]
            [Test2()]
            public object TestProperty { get; private set; }

            public TestObject(object propertyValue)
            {
                TestProperty = propertyValue;
            }

            [Test("Test")]
            public void Method1() 
            {}

            [Test("NotTest")]
            public void Method2()
            {}

            [Test("Test")]
            public void Method3()
            { }
        }

        private class TestAttribute : Attribute
        {
            public string TestProperty { get; private set;}
            
            public TestAttribute(string propertyValue)
            {
                TestProperty = propertyValue;
            }
        }

        private class Test2Attribute : Attribute
        { }

        private class Test3Attribute : Attribute
        { }
    }
}